#include "HttpRequest.h"
#include <sys/stat.h>
#include <dirent.h>
#include <assert.h>
#include <ctype.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include "Log.h"

// 实例化HttpRequest
HttpRequest::HttpRequest() { reset(); }

// 重置HttpRequest
void HttpRequest::reset() {
	_curState = reqProcessState::_ParseReqLine;
	_method = _url = _version = string();
	_reqHeaders.clear();
}

// 添加请求头键值对
void HttpRequest::addHeader(const string key, const string value) {
	if (key.empty() || value.empty()) { return; }
	_reqHeaders.insert(make_pair(key, value));
}

// 根据key得到value
string HttpRequest::getHeader(const string key) {
	auto it = _reqHeaders.find(key);
	if (it == _reqHeaders.end()) { return string(); }
	return it->second;
}

// 解析请求行
bool HttpRequest::parseRequestLine(Buffer* readBuf) {
	// 1. 读出请求行
	char* end = readBuf->findCRLF(); // 保存字符串结束地址
	char* start = readBuf->data();   // 获取可读数据的起始地址
	int lineSize = end - start;      // 获取行的长度

	// 2. 解析请求行
	if (lineSize) { // 字符串: get /xxx/xx.xx http/1.1
		auto methodFunc = bind(&HttpRequest::setMethod, this, placeholders::_1);
		auto urlFunc = bind(&HttpRequest::setUrl, this, placeholders::_1);
		auto versionFunc = bind(&HttpRequest::setVersion, this, placeholders::_1);
		start = splitRequestLine(start, end, " ", methodFunc);
		start = splitRequestLine(start, end, " ", urlFunc);
		splitRequestLine(start, end, nullptr, versionFunc);

		// 为解析请求报头作准备
		readBuf->readPosIncrease(lineSize + 2);
		setState(reqProcessState::_ParseReqHeaders);
		return true;
	}
	return false;
}

// 拆分请求行: 在[start, end)中找到第一个sub的起始地址space, 将[start, space)赋值给*ptr
char* HttpRequest::splitRequestLine(const char* start, const char* end, const char* sub, function<void(string)> callback) {
	char* space = const_cast<char*>(end);
	int sublen = 1;
	if (sub != nullptr) {
		sublen = strlen(sub);
		space = static_cast<char*>(memmem(start, end - start, sub, strlen(sub)));
		assert(space != NULL);
	}
	int length = space - start;
	callback(string(start, length));
	return space + sublen;
}

// 将字符转换为整形数
int HttpRequest::hexToDec(char c) {
	if (c >= '0' && c <= '9') { return c - '0'; }
	if (c >= 'a' && c <= 'f') { return c - 'a' + 10; }
	if (c >= 'A' && c <= 'F') { return c - 'A' + 10; }
	return 0;
}

// 将from译码处理为正确的内容
string HttpRequest::decodeMsg(string msg) {
	string res = string();
	const char* from = msg.data();
	for (; *from != '\0'; ++from) {
		// isxdigit -> 判断字符是不是16进制格式, 取值在0-f
		// Linux内核.jpg -> Linux%E5%86%85%E6%A0%B8.jpg
		if (from[0] == '%' && isxdigit(from[1]) && isxdigit(from[2])) {
			// 将16进制的数 -> 十进制, 将这个数值赋值给字符: int -> char
			// B2 = 178
			// 将3个字符变成1个字符, 该字符就是原始数据
			res.append(1, hexToDec(from[1]) * 16 + hexToDec(from[2]));

			// 跳过from[1]和from[2], 因为在当前循环中已经处理过了
			from += 2;
		}
		else { res.append(1, *from); } // 字符拷贝
	}
	res.append(1, '\0');
	return res;
}

// 获取文件name对应的Content-Type
const string HttpRequest::getFileType(const string name) {
	// name = "a.html"
	// 从右向左查找'.'字符, 否不存在则返回NULL
	const char* dot = strrchr(name.data(), '.');
	if (dot == NULL) {
		return "text/plain; charset=utf-8"; // 纯文本
	}
	else if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0) { return "text/html; charset=utf-8"; }
	else if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0) { return "image/jpeg"; }

	else if (strcmp(dot, ".gif") == 0) { return "image/gif"; }
	else if (strcmp(dot, ".png") == 0) { return "image/png"; }
	else if (strcmp(dot, ".css") == 0) { return "text/css"; }
	else if (strcmp(dot, ".au") == 0) { return "audio/basic"; }
	else if (strcmp(dot, ".wav") == 0) { return "audio/wav"; }
	else if (strcmp(dot, ".avi") == 0) { return "video/x-msvideo"; }

	else if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0) { return "video/quicktime"; }
	else if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0) { return "video/mpeg"; }
	else if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0) { return "model/vrml"; }
	else if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0) { return "audio/midi"; }

	else if (strcmp(dot, ".mp3") == 0) { return "audio/mpeg"; }
	else if (strcmp(dot, ".ogg") == 0) { return "application/ogg"; }

	return "text/plain; charset=utf-8";
}



// 解析[记录在Buffer的请求报头]中的某一行, 并存储到_reqHeaders
bool HttpRequest::parseRequestHeader(Buffer* readBuf) {
	char* lineEnd = readBuf->findCRLF();
	if (lineEnd != nullptr) {
		char* start = readBuf->data();
		int lineSize = lineEnd - start;
		// 基于': '分割字符串
		char* middle = static_cast<char*>(memmem(start, lineSize, ": ", 2));
		if (middle != nullptr) {
			int keyLen = middle - start;
			int valueLen = lineEnd - middle - 2;
			if (keyLen > 0 && valueLen > 0) {
				string key(start, keyLen);
				string value(middle + 2, valueLen);
				addHeader(key, value);
			}
			// 移动读取位置: 跳过\r\n
			readBuf->readPosIncrease(lineSize + 2);
		}
		else { // 请求头被解析完了, 跳过空行
			readBuf->readPosIncrease(2);
			setState(reqProcessState::_ParseReqDone); // 修改解析状态: 此处忽略了post请求，按照get请求处理
		}
		return true;
	}
	return false;
}

// 解析http请求, 解析成功会填充response, 并将http响应写入sendBuf
bool HttpRequest::parseHttpRequest(Buffer* readBuf, HttpResponse* response, Buffer* sendBuf, int sockfd) {
	bool flag = true;
	while (_curState != reqProcessState::_ParseReqDone) {
		switch (_curState) {
		case reqProcessState::_ParseReqLine:
			flag = parseRequestLine(readBuf);
			break;
		case reqProcessState::_ParseReqHeaders:
			flag = parseRequestHeader(readBuf);
			break;
		case reqProcessState::_ParseReqBody:
			break;
		default:
			break;
		}
		if (!flag) { return flag; }
		// 判断是否解析完毕了 -> 若解析完毕，就需要准备要回复的数据
		if (_curState == reqProcessState::_ParseReqDone) {
			processRequest(response);				// 处理http请求: 解析request，填充response
			response->prespareMsg(sendBuf, sockfd); // 构建Http响应，并发送到sendBuf
		}
	}
	_curState = reqProcessState::_ParseReqLine; // 状态还原: 保证还能处理之后的请求
	return true;
}



// 将文件写入sendBuf, 并将sendBuf中的数据通过sockfd发送出去
void HttpRequest::sendFile(string fileName, Buffer* sendBuf, int sockfd) {
	// 1. 打开文件
	int fileFd = open(fileName.data(), O_RDONLY);
	assert(fileFd > 0);

	while (1) {
		// 2. 读取1KB的文件内容, 并发送给客户端
		char buffer[4096];
		int len = read(fileFd, buffer, sizeof buffer);
		if (len > 0) {  // ��ȡ�ɹ�
			sendBuf->appendString(buffer, len);
#ifndef MSG_SEND_AUTO
			sendBuf->sendData(sockfd); // 将sendBuf中的数据发送出去
#endif
		}
		else if (len == 0) { break; } // 读取完毕 -> 结束循环
		else { perror("read");  break; }
	}
	close(fileFd);
}

/*
<html>
	<head>
		<title>test</title>
	</head>
	<body>
		<table>
			<tr>
				<td></td>
				<td></td>
			</tr>
			<tr>
				<td></td>
				<td></td>
			</tr>
		</table>
	</body>
</html>
*/
// 将目录写入sendBuf
void HttpRequest::sendDir(string dirName, Buffer* sendBuf, int sockfd) {
	// 1. 初始化需要返回的Html数据
	char buffer[4096] = { 0 };
	sprintf(buffer, "<html><head><title>%s</title></head><body><table>", dirName.data());

	// 2. 遍历目录, 完善Html数据
	struct dirent** fileList;
	int fileNum = scandir(dirName.data(), &fileList, NULL, alphasort);
	for (int i = 0; i < fileNum; ++i) {
		// [1] 取出文件名, fileList指向指针数组struct dirent* fileList[]
		char* name = fileList[i]->d_name;

		// [2] 获取dirName目录下的子文件的路径
		char subPath[1024] = { 0 };
		sprintf(subPath, "%s/%s", dirName.data(), name);
		LogDebug("%s", subPath);

		// [3] 根据子文件的路径, 获取子文件的文件信息
		struct stat st;
		stat(subPath, &st);
		if (S_ISDIR(st.st_mode)) { // 当前子文件是目录
			// a标签 <a href="">name</a>
			sprintf(buffer + strlen(buffer), "<tr><td><a href=\"%s/\">%s</a></td><td>%ld</td></tr>",
				name, name, st.st_size);
		}
		else { // 当前子文件不是目录
			sprintf(buffer + strlen(buffer), "<tr><td><a href=\"%s\">%s</a></td><td>%ld</td></tr>",
				name, name, st.st_size);
		}
		// [4] 将文件存入sendBuf, 并通过sockfd发送
		sendBuf->appendString(buffer);
#ifndef MSG_SEND_AUTO
		sendBuf->sendData(sockfd); // 将sendBuf中的数据发送出去
#endif
		memset(buffer, 0, sizeof buffer); // 不重复发送相同数据
		free(fileList[i]);
	} // end for

	// 3. 发送html数据的结尾
	sprintf(buffer, "</table></body></html>");
	sendBuf->appendString(buffer);
#ifndef MSG_SEND_AUTO
	sendBuf->sendData(sockfd); // 将sendBuf中的数据发送出去
#endif
	free(fileList);
}



// 处理http请求: 解析request，填充responses
bool HttpRequest::processRequest(HttpResponse* response) {
	// strcasecmp: 判断2字符串是否相等, 不区分大小写
	// 忽略Get以外的请求方式
	if (strcasecmp(_method.c_str(), "get") != 0) { return -1; }

	// 1. 将path解码处理为正确的文件路径
	_url = decodeMsg(_url);

	// 2. 处理客户端请求的静态资源(目录或文件)
	const char* file = NULL;
	if (strcmp(_url.c_str(), "/") == 0) { file = "./"; }
	else { file = _url.data() + 1; }

	// 3. 获取文件属性
	struct stat st;
	int ret = stat(file, &st);
	if (ret == -1) { // 文件不存在 -- 回复404
		// 状态行
		response->setFileName("404.html");
		response->setStatusCode(StatusCode::NotFound);
		// 响应头
		response->addHeader("Content-type", getFileType(".html"));
		response->_sendDataFunc = sendFile;
		return 0;
	}

	// 4. 判断文件类型
	response->setFileName(file);
	response->setStatusCode(StatusCode::OK);
	if (S_ISDIR(st.st_mode)) { // 请求的静态资源是目录
		response->addHeader("Content-type", getFileType(".html"));
		response->_sendDataFunc = sendDir;
	}
	else { // 请求的静态资源是普通文件
		response->addHeader("Content-type", getFileType(file));
		response->addHeader("Content-length", to_string(st.st_size));
		response->_sendDataFunc = sendFile;
	}
	return false;
}
